import * as ol from "ol";
import { Circle, Geometry } from "ol/geom";
import * as Layer from "ol/layer";
import { METERS_PER_UNIT } from "ol/proj";
import * as Source from "ol/source";
import { Fill, Stroke, Style, Text } from "ol/style";
import { queryFeatureId } from "./feature";
import * as keys from "./keys";
import { log } from "./log";
import {
  FeatureEventCallback,
  FillStyle,
  LabelStyle,
  RenderCircleLayerOptions,
  RenderCirclesOptions,
  StrokeStyle,
} from "./types";
import { parseLngLat } from "./utils";
import { excludeProperties } from "@utils";
import { getMap } from "./instance";

// 渲染圆形元素配置
export interface RenderCircleFeatureOptions {
  type?: string; // 圆形类型
  layerType?: string; // 图层类型
  name?: string; // 多边形名称
  center: number[]; // 中心点经纬度
  radius: number; // 半径，单位为米
  fillStyle?: FillStyle; // 填充样式
  strokeStyle?: StrokeStyle; // 描边样式
  label?: string; // 标记点文字
  labelStyle?: LabelStyle; // 文本样式
  extData?: any; // 额外数据
  stopClick?: boolean; // 是否停止点击事件冒泡
  stopDBClick?: boolean; // 是否停止双击事件冒泡
  onClick?: FeatureEventCallback; // 元素点击回调
  onDBClick?: FeatureEventCallback; // 元素双击回调
  onMouseEnter?: FeatureEventCallback; // 元素鼠标移入回调
  onMouseLeave?: FeatureEventCallback; // 元素鼠标移出回调
}

/**
 * @description: 渲染圆形元素
 * @param {RenderCircleFeatureOptions} options 配置项
 * @returns {ol.Feature}
 */
export const _renderCircleFeature = (
  options: RenderCircleFeatureOptions
): ol.Feature => {
  const {
    type = "default-polyline",
    layerType = "default-feature-layer",
    name,
    center,
    radius,
    fillStyle,
    strokeStyle,
    label,
    labelStyle,
    extData,
    stopClick = true,
    stopDBClick = true,
    onClick,
    onDBClick,
    onMouseEnter,
    onMouseLeave,
  } = options;
  const feature = new ol.Feature({
    name,
    geometry: new Circle(center, radius / METERS_PER_UNIT.degrees),
  });
  feature.setStyle(
    new Style({
      ...(fillStyle
        ? {
            fill: new Fill(fillStyle),
          }
        : undefined),
      ...(strokeStyle
        ? {
            stroke: new Stroke(strokeStyle),
          }
        : undefined),
      ...(label
        ? {
            text: new Text({
              text: label,
              ...labelStyle,
            }),
          }
        : undefined),
    })
  );

  // 设置自定义属性
  feature.set(keys.TYPE_KEY, "circle");
  feature.set(keys.FEATURE_ID_KEY, `feature-${queryFeatureId()}`);
  feature.set(keys.FEATURE_TYPE_KEY, type);
  feature.set(keys.LAYER_TYPE_KEY, layerType);
  feature.set(keys.FEATURE_EXTRA_DATA_KEY, extData);
  feature.set(keys.STOP_CLICK_KEY, !!stopClick);
  feature.set(keys.STOP_DBCLICK_KEY, !!stopDBClick);
  feature.set(keys.ON_CLICK_KEY, onClick);
  feature.set(keys.ON_DBCLICK_KEY, onDBClick);
  feature.set(keys.ON_MOUSE_ENTER_KEY, onMouseEnter);
  feature.set(keys.ON_MOUSE_LEAVE_KEY, onMouseLeave);
  feature.set(keys.OPTIONS_KEY, options);

  return feature;
};

/**
 * @description: 渲染圆形
 * @param {RenderCirclesOptions} options 配置项
 * @returns {Array<ol.Feature>}
 */
export const renderCircles = (options: RenderCirclesOptions): ol.Feature[] => {
  const mapInstance = getMap();
  if (!mapInstance) {
    throw new Error("地图实例未初始化");
  }

  const features: ol.Feature[] = [];
  const {
    type: defaultType,
    circles,
    fillStyle: defaultFillStyle,
    strokeStyle: defaultStrokeStyle,
    label: defaultLabel,
    labelStyle: defaultLabelStyle,
    stopClick: defaultStopClick,
    stopDBClick: defaultStopDBClick,
    onClick: defaultOnClick,
    onDBClick: defaultOnDBClick,
    onMouseEnter: defaultOnMouseEnter,
    onMouseLeave: defaultOnMouseLeave,
  } = options;

  const layer = mapInstance
    .getAllLayers()
    .find(
      (item) => item.get(keys.LAYER_TYPE_KEY) === "default-feature-layer"
    ) as Layer.Vector<Source.Vector<ol.Feature<Geometry>>> | undefined;

  log("Render Circles", options);

  if (Array.isArray(circles)) {
    circles.forEach(
      ({
        type,
        name,
        center,
        radius,
        fillStyle,
        strokeStyle,
        label,
        labelStyle,
        extData,
        stopClick,
        stopDBClick,
        onClick,
        onDBClick,
        onMouseEnter,
        onMouseLeave,
      }) => {
        const feature = _renderCircleFeature({
          type: type ?? defaultType,
          name,
          center: parseLngLat(center) as number[],
          radius,
          fillStyle: { ...defaultFillStyle, ...fillStyle },
          strokeStyle: { ...defaultStrokeStyle, ...strokeStyle },
          label: label ?? defaultLabel,
          labelStyle: { ...defaultLabelStyle, ...labelStyle },
          extData,
          stopClick: stopClick ?? defaultStopClick,
          stopDBClick: stopDBClick ?? defaultStopDBClick,
          onClick: onClick ?? defaultOnClick,
          onDBClick: onDBClick ?? defaultOnDBClick,
          onMouseEnter: onMouseEnter ?? defaultOnMouseEnter,
          onMouseLeave: onMouseLeave ?? defaultOnMouseLeave,
        });
        features.push(feature);
        layer?.getSource()?.addFeature(feature);
        if (type ?? defaultType) {
          layer?.set(
            keys.FEATURE_TYPES_KEY,
            Array.from(
              new Set([
                ...(layer?.get(keys.FEATURE_TYPES_KEY) ?? []),
                type ?? defaultType,
              ])
            )
          );
        }
      }
    );
  } else {
    const {
      type,
      name,
      center,
      radius,
      fillStyle,
      strokeStyle,
      label,
      labelStyle,
      extData,
      stopClick,
      stopDBClick,
      onClick,
      onDBClick,
      onMouseEnter,
      onMouseLeave,
    } = circles;
    const feature = _renderCircleFeature({
      type: type ?? defaultType,
      name,
      center: parseLngLat(center) as number[],
      radius,
      fillStyle: { ...defaultFillStyle, ...fillStyle },
      strokeStyle: { ...defaultStrokeStyle, ...strokeStyle },
      label: label ?? defaultLabel,
      labelStyle: { ...defaultLabelStyle, ...labelStyle },
      extData,
      stopClick: stopClick ?? defaultStopClick,
      stopDBClick: stopDBClick ?? defaultStopDBClick,
      onClick: onClick ?? defaultOnClick,
      onDBClick: onDBClick ?? defaultOnDBClick,
      onMouseEnter: onMouseEnter ?? defaultOnMouseEnter,
      onMouseLeave: onMouseLeave ?? defaultOnMouseLeave,
    });
    features.push(feature);
    layer?.getSource()?.addFeature(feature);
    if (type ?? defaultType) {
      layer?.set(
        keys.FEATURE_TYPES_KEY,
        Array.from(
          new Set([
            ...(layer?.get(keys.FEATURE_TYPES_KEY) ?? []),
            type ?? defaultType,
          ])
        )
      );
    }
  }

  return features;
};

/**
 * @description: 渲染圆形图层
 * @param {RenderCircleLayerOptions} options 配置项
 * @returns {Layer.Vector<Source.Vector<ol.Feature<Geometry>>>}
 */
export const renderCircleLayer = (
  options: RenderCircleLayerOptions
): Layer.Vector<Source.Vector<ol.Feature<Geometry>>> => {
  const mapInstance = getMap();
  if (!mapInstance) {
    throw new Error("地图实例未初始化");
  }
  const {
    type: layerType,
    circles,
    fillStyle: defaultFillStyle,
    strokeStyle: defaultStrokeStyle,
    label: defaultLabel,
    labelStyle: defaultLabelStyle,
    visible = true,
    clear = true,
    stopClick: defaultStopClick,
    stopDBClick: defaultStopDBClick,
    onClick: defaultOnClick,
    onDBClick: defaultOnDBClick,
    onMouseEnter: defaultOnMouseEnter,
    onMouseLeave: defaultOnMouseLeave,
  } = options;
  let layer = mapInstance
    .getAllLayers()
    .find((item) => item.get(keys.LAYER_TYPE_KEY) === layerType) as
    | Layer.Vector<Source.Vector<ol.Feature<Geometry>>>
    | undefined;
  if (!(layer instanceof Layer.Vector)) {
    layer = new Layer.Vector<Source.Vector<ol.Feature<Geometry>>>({
      source: new Source.Vector<ol.Feature<Geometry>>(),
      ...excludeProperties(options, [
        "type",
        "circles",
        "fillStyle",
        "strokeStyle",
        "label",
        "labelStyle",
        "clear",
        "stopClick",
        "stopDBClick",
        "onClick",
        "onDBClick",
        "onMouseEnter",
        "onMouseLeave",
      ]),
    });
    layer.set(keys.LAYER_TYPE_KEY, layerType);
    mapInstance.addLayer(layer);
  }

  log("Render Circle Layer", options);

  if (clear) {
    layer.getSource()?.clear();
  }

  if (Array.isArray(circles)) {
    circles.forEach(
      ({
        type,
        name,
        center,
        radius,
        fillStyle,
        strokeStyle,
        label,
        labelStyle,
        extData,
        stopClick,
        stopDBClick,
        onClick,
        onDBClick,
        onMouseEnter,
        onMouseLeave,
      }) => {
        layer?.getSource()?.addFeature(
          _renderCircleFeature({
            type,
            layerType,
            name,
            center: parseLngLat(center) as number[],
            radius,
            fillStyle: { ...defaultFillStyle, ...fillStyle },
            strokeStyle: { ...defaultStrokeStyle, ...strokeStyle },
            label: label ?? defaultLabel,
            labelStyle: { ...defaultLabelStyle, ...labelStyle },
            extData,
            stopClick: stopClick ?? defaultStopClick,
            stopDBClick: stopDBClick ?? defaultStopDBClick,
            onClick: onClick ?? defaultOnClick,
            onDBClick: onDBClick ?? defaultOnDBClick,
            onMouseEnter: onMouseEnter ?? defaultOnMouseEnter,
            onMouseLeave: onMouseLeave ?? defaultOnMouseLeave,
          })
        );
      }
    );
  } else {
    const {
      type,
      name,
      center,
      radius,
      fillStyle,
      strokeStyle,
      label,
      labelStyle,
      extData,
      stopClick,
      stopDBClick,
      onClick,
      onDBClick,
      onMouseEnter,
      onMouseLeave,
    } = circles;
    layer?.getSource()?.addFeature(
      _renderCircleFeature({
        type,
        layerType,
        name,
        center: parseLngLat(center) as number[],
        radius,
        fillStyle: { ...defaultFillStyle, ...fillStyle },
        strokeStyle: { ...strokeStyle, ...defaultStrokeStyle },
        label: label ?? defaultLabel,
        labelStyle: { ...defaultLabelStyle, ...labelStyle },
        extData,
        stopClick: stopClick ?? defaultStopClick,
        stopDBClick: stopDBClick ?? defaultStopDBClick,
        onClick: onClick ?? defaultOnClick,
        onDBClick: onDBClick ?? defaultOnDBClick,
        onMouseEnter: onMouseEnter ?? defaultOnMouseEnter,
        onMouseLeave: onMouseLeave ?? defaultOnMouseLeave,
      })
    );
  }
  layer.setVisible(visible);

  return layer;
};

/**
 * @description: 显示或渲染圆形图层
 * @param {RenderCircleLayerOptions} options 配置项
 * @returns {Layer.Vector<Source.Vector<ol.Feature<Geometry>>>}
 */
export const showOrRenderCircleLayer = (
  options: RenderCircleLayerOptions
): Layer.Vector<Source.Vector<ol.Feature<Geometry>>> => {
  const mapInstance = getMap();
  if (!mapInstance) {
    throw new Error("地图实例未初始化");
  }
  const { type: layerType, visible = true, clear = false } = options;
  const layer = mapInstance
    .getAllLayers()
    .find((item) => item.get(keys.LAYER_TYPE_KEY) === layerType) as
    | Layer.Vector<Source.Vector<ol.Feature<Geometry>>>
    | undefined;
  if (layer instanceof Layer.Vector && clear === false) {
    log("Change Circle Layer Visible", options);
    layer.setVisible(visible);
    return layer;
  }

  return renderCircleLayer(options);
};
